package com.aquarium.bt;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.util.Observable;
import java.util.Observer;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;
import java.util.concurrent.ArrayBlockingQueue;

import android.app.AlertDialog;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.util.Log;
import android.widget.Toast;

import com.aquarium.config.AlarmDefaultValue;
import com.aquarium.config.BTCommand;

public class BTManager extends Observable {
	private final static String TAG = "BTManager";
	private final static UUID COMMOM_UUID = UUID
			.fromString("00001101-0000-1000-8000-00805F9B34FB");

	public static final String DISCONNECTION_ACTION = "disconnect";

	private static BTManager mBTManager;
	private BluetoothAdapter mBluetoothAdapter;
	private BluetoothDevice mBluetoothDevice;
	private BluetoothSocket mBluetoothSocket;
	// private static RecvDataDispatcher mRecvDataDispatcher;
	private StatusChangedListener mStatusChangedListener;
	private RemoteDeviceListener mRemoteDeviceListener;
	private InputStream mInputStream;
	private OutputStream mOutputStream;
	// private DataInterpreter mDataInterpreter;
	private Thread mConnectThread;
	private Thread mRecvDataThread;
	private Thread mSendDataThread;
	private int mConnectAttempts;

	private ArrayBlockingQueue<DataStruct> mSendDataQueue;
	// private Handler mRecvHandler;
	private Boolean mIsInit;
	private int mStatus;
	// test code
	private Timer mSendSensorValueTimer;
	private Context mContext;

	public interface StatusChangedListener {
		void onChanged(int status);
	}

	public interface RemoteDeviceListener {
		// void onDiscoveryFinished();

		void onDeviceFound(BluetoothDevice device);

	}

	private BTManager() {
		mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
		mSendDataQueue = new ArrayBlockingQueue<DataStruct>(100);
		// setStatus(Status.OFF);
		mIsInit = false;
		Log.e(TAG, "mIsInit -> false");
		mConnectAttempts = 0;
	}

	public class Status {
		public static final int OFF = 0;
		public static final int TURNING_ON = 1;
		public static final int ON = 2;
		public static final int DISCOVERYING = 3;
		public static final int DISCOVERYING_FINISHED = 4;
		public static final int CONNECTING = 5;
		public static final int CONNECTED = 6;
		public static final int CONNECT_FAILED = 7;

	}

	public static BTManager getInstance() {
		if (mBTManager == null) {
			Log.e(TAG, "mBTManager == null");
			synchronized (BTManager.class) {
				if (mBTManager == null) {
					mBTManager = new BTManager();
				}
			}
		}
		Log.e(TAG, "return mBTManager:" + mBTManager);
		return mBTManager;
	}

	public void addObservers(Observer observer) {
		addObserver(observer);
	}

	/**
	 * Connect to remote BT device
	 */
	public void connect(final BluetoothDevice device) {
		boolean connectResult = false;
		mBluetoothDevice = device;
		if (!mIsInit) {
			Log.e(TAG, "connect(), do init() fitst!");
			return;
		}
		if (!isOpen()) {
			Log.i(TAG, "bluetooth does not open!");
			return;
		}
		// if (mStatus == Status.CONNECTING || mStatus == Status.CONNECTED) {
		// Log.i(TAG, "bluetooth is connecting or has been connected!");
		// return;
		// }
		// 连接前务必先关闭当前的连接

		if (mBluetoothSocket != null) {
			try {
				mBluetoothSocket.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

		Runnable r = new Runnable() {

			@Override
			public void run() {
				mConnectAttempts = 0;
				while (mConnectAttempts++ < 3) {
					try {
						mBluetoothSocket = device
								.createRfcommSocketToServiceRecord(COMMOM_UUID);
					} catch (IOException e) {
						Log.e(TAG, "create bluetooth socket failed!");
						e.printStackTrace();
					}
					mBluetoothAdapter.cancelDiscovery();
					try {
						setStatus(Status.CONNECTING);
						Log.i(TAG, "start to connect!");
						mBluetoothSocket.connect();
					} catch (IOException e) {
						Log.e(TAG, "connet to remote bluetooth device failed!");
						e.printStackTrace();
						try {
							Thread.sleep(500);
						} catch (InterruptedException e1) {
							e1.printStackTrace();
						}
						continue;
					}
					try {
						mInputStream = mBluetoothSocket.getInputStream();
						mOutputStream = mBluetoothSocket.getOutputStream();
					} catch (IOException e) {
						Log.e(TAG, "get data stream error!");
						e.printStackTrace();
						continue;
					}
					mConnectAttempts = 0;
					setStatus(Status.CONNECTED);
					startRecvDataThread();
					startSendDataThread();
					return;
				}
				// close();
				setStatus(Status.CONNECT_FAILED);
			}
		};
		mConnectThread = new Thread(r);
		mConnectThread.start();
	}

	/**
	 * Do initial. This must be called first.
	 * 
	 * @param c
	 *            此处必须是application的Context
	 */
	public void init(Context c) {

		if (mIsInit) {
			Log.w(TAG, "Has been init!");
			return;
		}
		mContext = c;
		IntentFilter filter = new IntentFilter(
				BluetoothAdapter.ACTION_STATE_CHANGED);
		c.registerReceiver(mReceiver, filter);
		filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
		c.registerReceiver(mReceiver, filter);
		filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
		c.registerReceiver(mReceiver, filter);
		filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
		c.registerReceiver(mReceiver, filter);
		filter = new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
		c.registerReceiver(mReceiver, filter);
		filter = new IntentFilter(BluetoothDevice.ACTION_ACL_DISCONNECTED);
		c.registerReceiver(mReceiver, filter);
		mIsInit = true;
		Log.e(TAG, "mIsInit --> true");

		if (isOpen()) {
			setStatus(Status.ON);
		}
	}

	/**
	 * Open BT device
	 */
	public void open() {
		if (!mIsInit) {
			Log.e(TAG, "open(), do init() fitst!");
			return;
		}
		if (mStatus == Status.OFF) {
			mBluetoothAdapter.enable();
		}
	}

	/**
	 * Close BT device and recycle resources
	 */
	public void close() {
		if (!mIsInit) {
			return;
		}
		mIsInit = false;
		setStatus(Status.OFF);
		mConnectAttempts = 0;
		mBluetoothAdapter.disable();
		mContext.unregisterReceiver(mReceiver);
		try {
			if (mBluetoothSocket != null) {
				mBluetoothSocket.close();
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		// 务必释放资源
		mBTManager = null;
	}

	// /**
	// * Activity在退出时应该取消注册Receiver
	// */
	// public void unregisterReceiver() {
	// if (!mIsInit) {
	// return;
	// }
	// mContext.unregisterReceiver(mReceiver);
	// }

	/**
	 * Is BT open
	 * 
	 * @return True if BT has opened
	 */
	public boolean isOpen() {
		if (!mIsInit) {
			Log.e(TAG, "isOpen(), do init() fitst!");
			return false;
		}
		return mBluetoothAdapter.isEnabled();
	}

	/**
	 * Send data to remote device
	 * 
	 * @param data
	 *            The data to send
	 */
	public void sendData(DataStruct data) {
		if (!mIsInit) {
			Log.e(TAG, "sendData(), do init() fitst!");
			return;
		}
		if (mStatus != Status.CONNECTED) {
			Log.e(TAG, "current statu is:" + mStatus);
			Log.e(TAG, "bluetooth does not connet!");
			return;
		}
		try {
			mSendDataQueue.put(data);
		} catch (InterruptedException e) {
			e.printStackTrace();
			Log.i(TAG, "put data to SendDataQueue failed!");
		}
	}

	/**
	 * Start a thread to discovery remote BT device
	 */
	public synchronized void startDiscovery() {
		if (!mIsInit) {
			Log.e(TAG, "startDiscovery(), do init() fitst!");
			return;
		}
		new Thread(new Runnable() {

			@Override
			public void run() {
				long t = System.currentTimeMillis();
				while (mStatus == Status.OFF || mStatus == Status.TURNING_ON) {
					if (System.currentTimeMillis() - t > 5000) {
						Log.e(TAG, "BT turn on fail!");
						return;
					}
				}
				if (mStatus == Status.DISCOVERYING) {
					Log.i(TAG, "Is Discoverying now!");
					return;
				}
				Log.i(TAG, "start discovery!");
				boolean result = mBluetoothAdapter.startDiscovery();
				if (!result) {
					Log.e(TAG, "start discovery remote bluetooth failed!");
				}
			}
		}).start();

	}

	private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
		@Override
		public void onReceive(Context context, Intent intent) {
			String action = intent.getAction();
			Log.i(TAG, "action found!! " + action);
			if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(action)) {
				setStatus(Status.DISCOVERYING);
			}
			if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
				final int status = intent.getIntExtra(
						BluetoothAdapter.EXTRA_STATE, -1);
				if (status == BluetoothAdapter.STATE_ON) {
					setStatus(Status.ON);
				} else if (status == BluetoothAdapter.STATE_OFF) {
					setStatus(Status.OFF);
				} else if (status == BluetoothAdapter.STATE_TURNING_ON) {
					setStatus(Status.TURNING_ON);
				} else if (status == BluetoothAdapter.STATE_DISCONNECTED) {
					Log.i(TAG, "Disconnected!");
					reConnect();
				}
			}
			if (BluetoothDevice.ACTION_FOUND.equals(action)) {
				BluetoothDevice device = intent
						.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);

				if (mRemoteDeviceListener != null) {
					mRemoteDeviceListener.onDeviceFound(device);
				}
				// if (device.getName().equals("BTCOM")) {
				// connect(device);
				// }
			} else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED
					.equals(action)) {
				setStatus(Status.DISCOVERYING_FINISHED);
				// setProgressBarIndeterminateVisibility(false);
				// setTitle(R.string.select_device);
				// if (mNewDevicesArrayAdapter.getCount() == 0) {
				// String noDevices = getResources().getText(
				// R.string.none_found).toString();
				// mNewDevicesArrayAdapter.add(noDevices);
				// }
			}
			// else if (BluetoothDevice.ACTION_ACL_DISCONNECTED.equals(action))
			// {
			// Log.i(TAG, "ACL disconnected!");
			// reConnect();
			// }
		}
	};

	/**
	 * Start a thread to send data or command
	 */
	private void startSendDataThread() {
		if (mStatus != Status.CONNECTED) {
			Log.e(TAG, "does not connect to any remote device!");
			return;
		}
		mSendDataThread = new Thread(new Runnable() {
			DataStruct data;
			byte[] bytes;

			@Override
			public void run() {
				Log.i(TAG, "mSendDataThread, Current state = " + mStatus);
				while (mStatus == Status.CONNECTED) {
					try {
						data = mSendDataQueue.take();
					} catch (InterruptedException e) {
						e.printStackTrace();
						Log.i(TAG, "take data failed!");
					}
					bytes = DataInterpreter.pack(data);
					try {
						mOutputStream.write(bytes);
					} catch (IOException e) {
						e.printStackTrace();
						Log.e(TAG, "send data error!");
						// continue;
					}
				}
			}
		});
		mSendDataThread.start();
	}

	/**
	 * Start a thread to receive data
	 */
	private void startRecvDataThread() {
		if (mStatus != Status.CONNECTED) {
			Log.e(TAG, "does not connect to any remote device!");
			return;
		}
		mRecvDataThread = new Thread(new Runnable() {

			@Override
			public void run() {
				byte[] buffer = new byte[1024];
				int bytes;

				Log.i(TAG, "mRecvDataThread, Current state = " + mStatus);
				while (mStatus == Status.CONNECTED) {
					try {
						bytes = mInputStream.read(buffer);
						Log.d(TAG, "read data from controller");
						// for (int i = 0; i < 10; i++) {
						// Log.d(TAG, i + String.format("%x", buffer[i]));
						// }
						setChanged();
						notifyObservers(DataInterpreter.unpack(buffer));
					} catch (IOException e) {
						Log.e(TAG, "read data error!", e);
						connectionLost();
						continue;
					}
				}
			}
		});
		mRecvDataThread.start();

	}

	private void connectionLost() {
		setStatus(Status.CONNECT_FAILED);
		mContext.sendBroadcast(new Intent(DISCONNECTION_ACTION));
		// TODO
	}

	private synchronized void reConnect() {
		setStatus(Status.CONNECTING);
		Thread thread = new Thread(new Runnable() {

			@Override
			public void run() {

				while (mStatus != Status.CONNECTED) {
					mStatus = Status.CONNECTING;
					Log.i(TAG,
							"receive BluetoothAdapter.STATE_DISCONNECTED, do connect now!");
					connect(mBluetoothDevice);
					try {
						Thread.sleep(2000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		});
		thread.start();
	}

	/**
	 * Reflect method. Start pairing to remote BT device.
	 */
	private boolean createBond(Class btClass, BluetoothDevice btDevice)
			throws Exception {
		Method createBondMethod = btClass.getMethod("createBond");
		Boolean returnValue = (Boolean) createBondMethod.invoke(btDevice);
		return returnValue.booleanValue();
	}

	/**
	 * Reflect method. Set the pin of BT pairing.
	 */
	private boolean setPin(Class btClass, BluetoothDevice btDevice, String str)
			throws Exception {
		try {
			Method removeBondMethod = btClass.getDeclaredMethod("setPin",
					new Class[] { byte[].class });
			Boolean returnValue = (Boolean) removeBondMethod.invoke(btDevice,
					new Object[] { str.getBytes() });
			Log.e("returnValue", "" + returnValue);
		} catch (SecurityException e) {
			// throw new RuntimeException(e.getMessage());
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			// throw new RuntimeException(e.getMessage());
			e.printStackTrace();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return true;

	}

	/**
	 * Reflect method. Cancel the input box of BT pairing.
	 */
	private boolean cancelPairingUserInput(Class btClass, BluetoothDevice device)
			throws Exception {
		Method createBondMethod = btClass.getMethod("cancelPairingUserInput");
		// cancelBondProcess()
		Boolean returnValue = (Boolean) createBondMethod.invoke(device);
		return returnValue.booleanValue();
	}

	/**
	 * Set an Recv Data Handler
	 * 
	 * @param handler
	 */
	// public void setRecvHandler(Handler handler) {
	// if (!mIsInit) {
	// Log.e(TAG, "setRecvHandler(), do init() fitst!");
	// return;
	// }
	// mRecvHandler = handler;
	// }

	/**
	 * Set current BT status
	 * 
	 * @param status
	 *            Current BT status to set
	 */
	private void setStatus(int status) {
		Log.i(TAG, "set status : " + status);
		mStatus = status;
		if (mStatusChangedListener != null) {
			mStatusChangedListener.onChanged(mStatus);
		}
	}

	/**
	 * Set a listener to be called back once the status changed
	 * 
	 * @param l
	 *            The listener to set
	 */
	public void setStatusChangedListener(StatusChangedListener l) {
		mStatusChangedListener = l;
	}

	/**
	 * Set a remote device listener
	 * 
	 * @param l
	 *            The listener to set
	 */
	public void setRemoteDeviceListener(RemoteDeviceListener l) {
		mRemoteDeviceListener = l;
	}

	/*********************************** test code **************************************/

	// public void sendHpAlarmValue(float min, float max) {
	// DataStruct struct = DataInterpreter.unpack(BTCommand
	// .getSetPhRangeCommand(min, max));
	// setChanged();
	// notifyObservers(struct);
	// }
	//
	// public void sendTemperatureAlarmValue(float min, float max) {
	// DataStruct struct = DataInterpreter.unpack(BTCommand
	// .getSetTemperatureRangeCommand(min, max));
	// setChanged();
	// notifyObservers(struct);
	// }
	//
	// public void sendOxygenAlarmValue(float min, float max) {
	// DataStruct struct = DataInterpreter.unpack(BTCommand
	// .getSetOxygenRangeCommand(min, max));
	// setChanged();
	// notifyObservers(struct);
	// }
	//
	// public void sendConductivityAlarmValue(float min, float max) {
	// DataStruct struct = DataInterpreter.unpack(BTCommand
	// .getSetConductivityRangeCommand(min, max));
	// setChanged();
	// notifyObservers(struct);
	// }
	//
	// public void sendWaterLevelAlarmValue(float min, float max) {
	// DataStruct struct = DataInterpreter.unpack(BTCommand
	// .getSetWaterLevelRangeCommand(min, max));
	// setChanged();
	// notifyObservers(struct);
	// }

	/**
	 * test code
	 */
	public void sendPasswdVarifyOk() {
		DataStruct struct = DataInterpreter
				.unpack(BTCommand.VARIFY_PASSWORD_SUCESS);
		setChanged();
		notifyObservers(struct);
		sendData(struct);
	}

	/**
	 * test code
	 */
	public void sendPasswdVarifyFail() {
		DataStruct struct = DataInterpreter
				.unpack(BTCommand.VARIFY_PASSWORD_FAIL);
		setChanged();
		notifyObservers(struct);
		sendData(struct);
	}

	/**
	 * test code
	 */
	public void sendSensorValueTest() {
		// if(true){
		// return;
		// }
		if (mSendSensorValueTimer != null) {
			mSendSensorValueTimer.cancel();
		}
		mSendSensorValueTimer = new Timer();
		mSendSensorValueTimer.schedule(new TimerTask() {

			@Override
			public void run() {
				DataStruct struct = new DataStruct();
				struct.prefix = 0x5a;
				struct.id = 0x01;
				struct.code = BTCommand.CODE_GET_PH_VALUE;
				struct.length = BTCommand.PH_DATA_LENGTH;
				struct.data = BTCommand.getSetPHCommand(getRandamFloat(
						AlarmDefaultValue.MINPH - 0.3f,
						AlarmDefaultValue.MAXPH + 0.3f, 0));
				setChanged();
				notifyObservers(struct);
				// also send this to controller for demo.
				sendData(struct);
				struct = new DataStruct();
				struct.prefix = 0x5a;
				struct.id = 0x01;
				struct.code = BTCommand.CODE_GET_TEMPERATURE_VALUE;
				struct.length = BTCommand.TEMPRATURE_DATA_LENGTH;
				struct.data = BTCommand
						.getSetTemperatureCommand(getRandamFloat(
								AlarmDefaultValue.MINTEMPERATURE - 10f,
								AlarmDefaultValue.MAXTEMPERATURE + 10f, 0));
				setChanged();
				notifyObservers(struct);
				sendData(struct);
				struct = new DataStruct();
				struct.prefix = 0x5a;
				struct.id = 0x01;
				struct.code = BTCommand.CODE_GET_OXYGEN_VALUE;
				struct.length = BTCommand.OXYGEN_DATA_LENGTH;
				struct.data = BTCommand.getSetOxygenCommand(getRandamFloat(
						AlarmDefaultValue.MINOxygen - 10f,
						AlarmDefaultValue.MAXOxygen + 10f, 0));
				setChanged();
				notifyObservers(struct);
				sendData(struct);
				struct = new DataStruct();
				struct.prefix = 0x5a;
				struct.id = 0x01;
				struct.code = BTCommand.CODE_GET_CONDUCTIVITY_VALUE;
				struct.length = BTCommand.CONDUCTIVITY_DATA_LENGTH;
				struct.data = BTCommand
						.getSetConductivityCommand(getRandamFloat(
								AlarmDefaultValue.MINConductivity - 0.005f,
								AlarmDefaultValue.MAXConductivity + 0.005f, 3));
				setChanged();
				notifyObservers(struct);
				sendData(struct);
				struct = new DataStruct();
				struct.prefix = 0x5a;
				struct.id = 0x01;
				struct.code = BTCommand.CODE_GET_WATER_LEVEL_VALUE;
				struct.length = BTCommand.WATER_LEVEL_DATA_LENGTH;
				struct.data = BTCommand.getSetWaterLevelCommand(getRandamFloat(
						AlarmDefaultValue.MINWATERLEVEL - 10f,
						AlarmDefaultValue.MAXWATERLEVEL + 10f, 0));
				setChanged();
				notifyObservers(struct);
				sendData(struct);
			}
		}, 0, 5000);

	}

	/**
	 * test code
	 */
	public void stopSendSensorValueTest() {
		if (mSendSensorValueTimer == null) {
			return;
		}
		mSendSensorValueTimer.cancel();
	}

	/**
	 * test code
	 * 
	 * @param min
	 * @param max
	 * @return
	 */
	private float getRandamFloat(float min, float max, int accurancy) {
		float value = (float) (Math.random() * (max - min) + min);
		value = (float) ((int) (value * Math.pow(10, accurancy)) / Math.pow(10,
				accurancy));
		return value;
	}

	/*********************************** test code **************************************/

	public void receivePseudoData(byte[] cmd) {
		DataStruct struct = DataInterpreter.unpack(cmd);
		setChanged();
		notifyObservers(struct);
		sendData(struct);
	}

}
